package keer.httpinterface.proxy;

import keer.httpinterface.HttpInterceptor;
import keer.httpinterface.HttpInterfaceException;
import keer.httpinterface.HttpInterfaceInterceptedException;
import keer.httpinterface.annotation.*;
import keer.httpinterface.http.*;
import keer.httpinterface.utils.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * http请求注解配置器基类，用于解析注解，并生成{@link HttpRequest}形式的http请求对象
 * @see AnnotationConfigure
 * @see HttpRequest
 *
 * @author Keer
 * @since 1.0.0
 */
@SuppressWarnings("unchecked")
public abstract class HttpRequestAnnotationConfigure implements AnnotationConfigure<HttpRequest> {
    private static final String KEY_URL = "url";
    private static final String KEY_HEADERS = "headers";
    private static final String KEY_PARAMS = "params";
    private static final String KEY_METHOD = "method";
    private static final String KEY_CHARSET = "charset";
    private static final String KEY_CONTENT_TYPE = "contentType";
//    private static final String KEY_CONTENT_TYPE_STRING = "contentTypeString";
    private static final String KEY_INTERCEPTORS = "interceptors";

    private static final CacheMap<String, InfoHolder> INFO_CACHE_MAP = new CacheMap<>();
    private static final CacheMap<Class<? extends HttpRequestAnnotationConfigure>, HttpRequestAnnotationConfigure> CONFIGURE_CACHE_MAP = new CacheMap<>();

    @Override
    public HttpRequest configure(AnnotationContext context, Object[] args) throws Exception {
        InfoHolder info = getInfo(context);
        Headers headers = getHeaders(info, args);
        HttpParams params = getParams(info, args);
        String url = getUrl(info, args);
        ConfigureContext ctx = new ConfigureContext(url, headers, params, info.httpMethod, info.contentType);
        HttpRequest request = configure(ctx);
//        request.getHeaders().putAll(ctx.headers);
        for (HttpInterceptor interceptor : info.interceptors) {
            if (!interceptor.intercept(request)) {
                throw new HttpInterfaceInterceptedException();
            }
        }
        return request;
    }

    /**
     * 根据传入的参数和信息持有者对象来构建和返回一个格式化后的URL
     *
     * @param holder 包含URL模板和其他信息的对象
     * @param args   函数接收到的参数数组，用于替换URL中的变量
     * @return 格式化后包含路径参数的URL
     * @throws IllegalAccessException 当访问字段失败时抛出此异常
     */
    private String getUrl(InfoHolder holder, Object[] args) throws IllegalAccessException {
        // 获取URL模板
        String url = holder.url;
        // 创建一个有序映射来存储路径参数
        Map<String, String> pathParams = new LinkedHashMap<>();

        // 遍历POJO类型的路径参数，从对象中提取值
        for (ArgInfo info : holder.pojoPathArgs) {
            Object o = args[info.index];
            if (o == null) {
                continue;
            }
            Field field = info.field;
            field.setAccessible(Boolean.TRUE);
            Object value = field.get(o);
            pathParams.put(info.name, value == null ? "null" : value.toString());
        }

        // 处理基本类型的路径参数
        for (ArgInfo info : holder.pathArgs) {
            Object o = args[info.index];
            pathParams.put(info.name, o == null ? "" : o.toString());
        }

        // 替换URL模板中的占位符为实际的路径参数值
        for (String key : pathParams.keySet()) {
            url = url.replace("{" + key + "}", pathParams.get(key));
        }

        // 返回构建完成的URL
        return url;
    }

    /**
     * 根据请求信息和参数构建请求头
     * 此方法用于组装HTTP请求的头部信息，包括固定的头部信息和根据请求参数动态生成的头部信息
     *
     * @param holder 请求信息持有者，包含固定的头部信息和内容类型
     * @param args   请求参数数组，从中提取头部参数
     * @return 返回构建好的请求头对象
     */
    private Headers getHeaders(InfoHolder holder, Object[] args) {
        // 初始化请求头对象，加载固定的头部信息
        Headers headers = new Headers(holder.headers);

        // 遍历需要动态添加的头部参数信息，从请求参数数组中提取值并添加到请求头中
        for (ArgInfo info : holder.headerArgs) {
            Object p = args[info.index];
            // 将参数值转换为字符串添加到请求头中，如果值为null，则使用空字符串
            headers.put(info.name, p == null ? "" : p.toString());
        }

        // 添加内容类型到请求头中，这是HTTP请求的必须头部信息
        headers.put("Content-type", holder.contentType.toString());

        // 返回构建好的请求头对象
        return headers;
    }

    /**
     * 根据提供的信息构建参数对象
     * 该方法从InfoHolder对象中提取信息，并结合传入的参数数组，构造一个新的Params对象
     *
     * @param holder 包含参数信息和参数详情的对象
     * @param args   参数数组，从中获取具体参数值
     * @return 返回一个填充了具体参数值的Params对象
     */
    private HttpParams getParams(InfoHolder holder, Object[] args) {
        // 初始化Params对象，使用InfoHolder中的参数信息
        HttpParams params = new HttpParams(holder.params);

        // 遍历InfoHolder中的每个参数信息
        for (ArgInfo info : holder.args) {
            // 根据参数信息中的名称和索引，从参数数组中获取对应的值，并添加到Params对象中
            params.addParameter(info.name, args[info.index]);
        }

        // 返回填充完毕的Params对象
        return params;
    }

    /**
     * 根据注解上下文获取信息持有者对象
     * 此方法首先会根据注解上下文中的目标类、目标方法和注解类型生成一个唯一的键，
     * 然后使用这个键调用另一个方法来获取或创建一个信息持有者对象
     *
     * @param context 注解上下文，包含了目标类、目标方法和注解类型等信息
     * @return 返回一个InfoHolder对象，它持有与给定注解上下文相关的信息
     * @throws Exception 如果在处理过程中遇到任何错误，此异常将被抛出
     */
    private InfoHolder getInfo(AnnotationContext context) throws Exception {
        // 生成唯一键：结合目标类名、目标方法哈希值和注解类型名
        String key = context.getTargetClass().getName() + "_" + context.getTargetMethod().hashCode() + "_" + context.getAnnotation().getAnnotationType().getName();
        // 使用生成的键获取或创建InfoHolder对象
        return getOrCreateInfoHolder(key, context);
    }

    /**
     * 获取或创建信息持有者对象
     * 该方法首先尝试从缓存中加载现有信息持有者（InfoHolder）对象，
     * 如果缓存中不存在，则创建一个新的InfoHolder对象，该对象包含处理HTTP请求所需的所有信息
     *
     * @param key     用于缓存中查找InfoHolder的键，通常是目标类的全限定名
     * @param context 包含目标类和目标方法的注解上下文，用于提取注解信息
     * @return 返回一个包含HTTP请求处理信息的InfoHolder对象
     * @throws Exception 如果在创建InfoHolder过程中发生错误，则抛出异常
     */
    private InfoHolder getOrCreateInfoHolder(String key, AnnotationContext context) throws Exception {
        InfoHolder holder = INFO_CACHE_MAP.loadOrCreate(key, () -> {
            try {
                // 获取目标类上的HttpInterface注解信息
                AnnotationInfo httpInterface = AnnotationUtils.getDeclaredAnnotation(context.getTargetClass(), HttpInterface.class, Boolean.TRUE);
                assert httpInterface != null;
                String basicUrl = (String) httpInterface.get(KEY_URL);
                Headers headers = new Headers(readHeaders(httpInterface));
                HttpParams params = new HttpParams(readParams(httpInterface));

                // 获取目标方法上的Request注解信息
                AnnotationInfo request = AnnotationUtils.getMethodAnnotation(context.getTargetMethod(), Request.class, Boolean.TRUE);
                String url = basicUrl + request.get(KEY_URL);
                headers.putAll(readHeaders(request));
                params.putAll(readParams(request));

                // 加载并实例化拦截器
                Class<? extends HttpInterceptor>[] interceptorClasses = (Class<? extends HttpInterceptor>[]) httpInterface.get(KEY_INTERCEPTORS);
                List<Class<? extends HttpInterceptor>> interceptorsList = new ArrayList<>(Arrays.asList(interceptorClasses));
                Class<? extends HttpInterceptor>[] requestInterceptorClasses = (Class<? extends HttpInterceptor>[]) request.get(KEY_INTERCEPTORS);
                interceptorsList.addAll(Arrays.asList(requestInterceptorClasses));
                List<HttpInterceptor> interceptors = new ArrayList<>();
                for (Class<? extends HttpInterceptor> clazz : interceptorsList) {
                    interceptors.add(InterceptorFactory.getInstance().create(clazz));
                }

                // 处理内容类型
                String contentTypeString = (String) request.get(KEY_CONTENT_TYPE);
                HttpContentType ct = HttpContentType.of(contentTypeString + ";charset=" + request.get(KEY_CHARSET));

                // 创建并返回InfoHolder对象
                InfoHolder h = new InfoHolder(url,
                        context.getTargetMethod(),
                        (HttpMethod) request.get(KEY_METHOD),
                        headers,
                        params,
                        ct,
                        interceptors);
                setArgs(context.getTargetMethod(), h);
                return h;
            } catch (Exception e) {
                return new InfoHolder(e);
            }
        });
        if (holder.error != null) {
            throw holder.error;
        }
        return holder;
    }

    /**
     * 解析方法参数注解并设置相关参数信息
     * 该方法负责读取方法参数上的注解，并根据这些注解生成相应的参数信息对象，
     * 最后将这些对象添加到InfoHolder实例中，以便后续处理使用
     *
     * @param method 方法对象，用于获取参数注解和参数名称
     * @param holder 信息持有者对象，用于存储解析后的参数信息
     */
    private static void setArgs(Method method, InfoHolder holder) {
        // 获取方法参数上的注解数组
        Annotation[][] annotations = method.getParameterAnnotations();
        // 获取方法的参数对象数组
        Parameter[] parameters = method.getParameters();
        // 遍历注解数组
        for (int i = 0; i < annotations.length; i++) {
            Annotation[] as = annotations[i];
            // 遍历当前参数上的所有注解
            for (Annotation a : as) {
                // 判断当前注解是否为Arg类型
                if (a.annotationType().equals(Arg.class) ||
                        a.annotationType().equals(NamedArg.class)) {
                    Arg aa = (Arg) a;
                    addArg(holder, i, aa.value(), parameters[i].getType());
                // 判断当前注解是否为NamedArg类型
                } else if (a.annotationType().equals(NamedArg.class)) {
                    NamedArg aa = (NamedArg) a;
                    addArg(holder, i, aa.value(), parameters[i].getType());
                // 判断当前注解是否为PathArg类型
                } else if (a.annotationType().equals(PathArg.class)) {
                    PathArg aa = (PathArg) a;
                    // 创建并添加PathArg类型的ArgInfo对象到holder的pathArgs列表中
                    holder.pathArgs.add(new ArgInfo(i, aa.value()));
                    // 判断当前注解是否为HeaderArg类型
                } else if (a.annotationType().equals(HeaderArg.class)) {
                    HeaderArg aa = (HeaderArg) a;
                    // 创建并添加HeaderArg类型的ArgInfo对象到holder的headerArgs列表中
                    holder.headerArgs.add(new ArgInfo(i, aa.value()));
                }
            }
        }
    }

    private static void addArg(InfoHolder holder, int index, String paramName, Class<?> paramType) {
        // 创建ArgInfo对象，并添加到holder的args列表中
        holder.args.add(new ArgInfo(index, paramName));
        // 如果当前参数类型不是基本类型或其包装类
        if (!ClassUtils.isPrimitiveOrWrapperClass(paramType)) {
            // 获取当前参数类型中带有PathArg注解的字段
            List<Field> fields = AnnotationUtils.getDeclaredFieldsAnnotationPresent(paramType, PathArg.class, Boolean.TRUE, Boolean.TRUE);
            // 遍历这些字段，创建并添加PathArg类型的ArgInfo对象到holder的pojoPathArgs列表中
            for (Field field : fields) {
                PathArg p = field.getAnnotation(PathArg.class);
                holder.pojoPathArgs.add(new ArgInfo(index, StringUtils.isBlank(p.value()) ? field.getName() : p.value(), field));
            }
        }
    }

    /**
     * 配置HTTP请求的抽象方法
     * <p>
     * 此方法需要由子类实现，用于在发送请求前对HTTP请求进行必要的配置
     * 它允许开发者在请求发送之前自定义请求的各个方面，如URL、请求头、请求方法等
     *
     * @param context 配置上下文，包含了一些配置请求所需的上下文信息
     * @return HttpRequest 配置好的HTTP请求对象
     * @throws Exception 如果配置过程中发生错误，抛出此异常
     */
    protected abstract HttpRequest configure(ConfigureContext context) throws Exception;

    /**
     * 从注解信息中读取HTTP头部信息
     * 此方法主要用于处理注解中指定的HTTP头部信息，将其提取并封装到Headers对象中
     *
     * @param annotation 注解信息对象，用于获取注解中的HTTP头部信息
     * @return 返回一个Headers对象，其中包含了从注解中提取的HTTP头部信息
     */
    private Headers readHeaders(AnnotationInfo annotation) {
        // 从注解中获取名为KEY_HEADERS的属性值，并将其转换为Header数组
        Header[] headers = (Header[]) annotation.get(KEY_HEADERS);
        // 创建一个Headers对象，用于存储HTTP头部信息
        Headers hs = new Headers();
        // 遍历Header数组，将每个Header对象的value值添加到Headers对象中
        for (Header header : headers) {
            hs.put(header.value());
        }
        // 返回封装了HTTP头部信息的Headers对象
        return hs;
    }

    /**
     * 从注解信息中读取参数并构建Params对象
     * 此方法主要用于处理注解中的参数，将其提取并封装到Params对象中
     *
     * @param annotation 注解信息对象，包含需要的参数信息
     * @return 返回构建好的Params对象，包含从注解信息中提取的参数
     */
    private HttpParams readParams(AnnotationInfo annotation) {
        // 获取注解中名为KEY_PARAMS的参数数组
        Param[] params = (Param[]) annotation.get(KEY_PARAMS);
        // 创建一个新的Params对象来存储参数
        HttpParams ps = new HttpParams();
        // 遍历参数数组，将每个参数的值添加到Params对象中
        for (Param param : params) {
            // 将参数的值作为键和值添加到Params对象中
//            ps.setParameter(param.value(), param.value());
            ps.addParameter(param.value(), param.value());
        }
        // 返回构建好的Params对象
        return ps;
    }

    /**
     * 封装参数信息类，用于存储参数的索引和名称
     *
     * @author Keer
     * @since 1.0.0
     */
    private static class ArgInfo {
        /**
         * 参数索引
         */
        private final int index;
        /**
         * 参数名称
         */
        private final String name;
        /**
         * 参数对应的字段
         */
        private final Field field;

        private ArgInfo(int index, String name) {
            this(index, name, null);
        }

        private ArgInfo(int index, String name, Field field) {
            this.index = index;
            this.name = name;
            this.field = field;
        }
    }

    /**
     * 配置HTTP请求上下文的静态内部类
     * 该类用于封装HTTP请求的相关配置信息，包括URL、请求头、参数、HTTP方法和内容类型
     */
    protected static class ConfigureContext {
        // 请求的URL
        private final String url;
        // 请求的头部信息
        private final Headers headers;
        // 请求的参数
        private final HttpParams params;
        // HTTP请求方法（如GET、POST等）
        private final HttpMethod method;
        // HTTP请求的内容类型
        private final HttpContentType contentType;

        /**
         * ConfigureContext类的私有构造方法
         * 用于初始化HTTP请求的配置信息
         *
         * @param url         请求的URL
         * @param headers     请求的头部信息
         * @param params      请求的参数
         * @param method      HTTP请求方法
         * @param contentType HTTP请求的内容类型
         */
        private ConfigureContext(String url,
                                 Headers headers,
                                 HttpParams params,
                                 HttpMethod method,
                                 HttpContentType contentType) {
            this.method = method;
            this.contentType = contentType;
            this.url = url;
            this.headers = headers;
            this.params = params;
        }

        /**
         * 获取请求的头部信息
         *
         * @return 请求的头部信息
         */
        public Headers getHeaders() {
            return headers;
        }

        /**
         * 获取请求的参数
         *
         * @return 请求的参数
         */
        public HttpParams getParams() {
            return params;
        }

        /**
         * 获取请求的URL
         *
         * @return 请求的URL
         */
        public String getUrl() {
            return url;
        }

        /**
         * 获取HTTP请求方法
         *
         * @return HTTP请求方法
         */
        public HttpMethod getMethod() {
            return method;
        }

        /**
         * 获取HTTP请求的内容类型
         *
         * @return HTTP请求的内容类型
         */
        public HttpContentType getContentType() {
            return contentType;
        }
    }

    /**
     * 信息持有者类，用于封装HTTP请求的相关信息
     */
    protected static class InfoHolder {
        // URL地址
        private final String url;
        // HTTP头部信息
        private final Headers headers;
        // 请求参数
        private final HttpParams params;
        // 方法对象
        private final Method method;
        // HTTP请求方法
        private final HttpMethod httpMethod;
        // HTTP内容类型
        private final HttpContentType contentType;
        // 路径参数信息列表
        private final List<ArgInfo> pathArgs;
        // POJO路径参数信息列表
        private final List<ArgInfo> pojoPathArgs;
        // 参数信息列表
        private final List<ArgInfo> args;
        // 头部参数信息列表
        private final List<ArgInfo> headerArgs;
        // 错误信息
        private Exception error;
        // 拦截器列表
        private final List<HttpInterceptor> interceptors;

        /**
         * 构造方法，用于初始化InfoHolder对象
         *
         * @param url          请求的URL地址
         * @param method       方法对象
         * @param httpMethod   HTTP请求方法
         * @param headers      HTTP头部信息
         * @param params       请求参数
         * @param contentType  HTTP内容类型
         * @param interceptors 拦截器列表
         */
        private InfoHolder(String url,
                           Method method,
                           HttpMethod httpMethod,
                           Headers headers,
                           HttpParams params,
                           HttpContentType contentType,
                           List<HttpInterceptor> interceptors) {
            this.interceptors = interceptors;
            this.pojoPathArgs = new ArrayList<>();
            this.headers = headers;
            this.params = params;
            this.contentType = contentType;
            this.httpMethod = httpMethod;
            this.url = url;
            this.method = method;
            this.pathArgs = new ArrayList<>();
            this.args = new ArrayList<>();
            this.headerArgs = new ArrayList<>();
        }

        /**
         * 构造方法，用于处理错误情况
         *
         * @param ex 错误信息
         */
        private InfoHolder(Exception ex) {
            // 调用另一个构造方法初始化成员变量
            this(null, null, null, null, null, null, null);
            this.error = ex;
        }
    }

    /**
     * 获取指定类的实例，如果该类的实例尚未创建，则会创建一个新的实例
     * 此方法主要用于延迟初始化和缓存配置类实例，以提高性能和减少资源消耗
     *
     * @param configureClass 配置类的类型，该类继承自 HttpRequestAnnotationConfigure
     * @return 返回配置类的实例
     */
    public static HttpRequestAnnotationConfigure getConfigure(Class<? extends HttpRequestAnnotationConfigure> configureClass) {
        return CONFIGURE_CACHE_MAP.loadOrCreate(configureClass, () -> {
            try {
                // 使用反射创建配置类的新实例
                return configureClass.newInstance();
            } catch (Exception e) {
                // 如果实例化失败，抛出运行时异常
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 根据注解配置信息构建HttpRequest对象
     *
     * @param context 注解上下文，包含了注解相关的元数据
     * @param args    方法的参数数组，可能包含配置所需的信息
     * @return HttpRequest对象，根据注解配置信息初始化
     * @throws Exception 如果配置过程中发生错误，则抛出异常
     */
    public static HttpRequest doConfigure(AnnotationContext context, Object[] args) throws Exception {
        // 从注解上下文中获取配置类的Class对象
        Class<? extends HttpRequestAnnotationConfigure> configureClass = (Class<? extends HttpRequestAnnotationConfigure>) context.getAnnotation().get("configure");
        // 使用反射实例化配置类，并调用其configure方法进行配置
        return getConfigure(configureClass).configure(context, args);
    }
}

